Private spaces implementation#1339
Conversation
…the private space
…vate-spaces into private-spaces
…oogle for viewing all materials
…ce (and all the others)
…he user is part of from the main app
…zed users in main app
There was a problem hiding this comment.
Pull request overview
This PR introduces “private spaces” backed by a new group/membership system, and threads the resulting access-control rules through Pundit policies, controllers (space resolution + search/index filtering), UI, and tests.
Changes:
- Adds Group/GroupMembership models + CRUD UI, plus authorization rules that allow read access to members/admins and restrict write access to owners/admins (and block API writes).
- Adds
spaces.is_privateand space↔group association, with sharedApplicationPolicy#shown?visibility logic and private-space enforcement inApplicationController#set_current_space. - Filters indexed/search results (incl. Solr-backed listings) by
shown?, and adds documentation + test coverage for the private-space access scenario.
Reviewed changes
Copilot reviewed 53 out of 55 changed files in this pull request and generated 14 comments.
Show a summary per file
| File | Description |
|---|---|
| test/system/groups_test.rb | System tests for basic Groups UI flows (index/create/update/destroy). |
| test/models/space_test.rb | Model validation test for “private space must have required groups”. |
| test/models/group_test.rb | Placeholder Group model test file. |
| test/integration/private_space_access_test.rb | Integration-style coverage for private-space access across spaces/materials surfaces. |
| test/fixtures/groups.yml | Adds Group fixtures used by tests. |
| test/controllers/groups_controller_test.rb | Controller-level tests for Group authorization + API write restrictions. |
| Gemfile | Adds explicit rdoc (>= 8.0) dependency. |
| Gemfile.lock | Updates lockfile for rdoc/rbs/prism-related dependencies. |
| docs/spaces.md | Documents private spaces behavior and deletion/nullification semantics. |
| docs/docstrings.md | Adds RDoc/docstring conventions documentation. |
| db/schema.rb | Adds groups, group_memberships, groups_spaces, and spaces.is_private to schema. |
| db/migrate/20260612065213_create_groups.rb | Creates groups table. |
| db/migrate/20260612065245_create_join_table_groups_users.rb | Creates interim groups↔users join table (later replaced). |
| db/migrate/20260612065251_create_join_table_groups_spaces.rb | Creates groups↔spaces join table. |
| db/migrate/20260618090239_add_is_private_to_spaces.rb | Adds is_private boolean to spaces. |
| db/migrate/20260618141208_replace_groups_users_with_group_memberships.rb | Replaces groups_users with group_memberships join model (with owner flag). |
| config/routes.rb | Adds RESTful routes for groups. |
| config/locales/en.yml | Updates “spaces” copy to mention private spaces/groups. |
| config/application.rb | Whitespace-only change. |
| app/views/users/show.html.erb | Adds a “Groups” section to the user sidebar with collapse/overflow UI. |
| app/views/users/_list.json.jbuilder | Alters /users.json list output (now excluding admins). |
| app/views/spaces/edit.html.erb | Whitespace-only change. |
| app/views/spaces/_form.html.erb | Adds private-space toggle and groups autocompleter; includes JS. |
| app/views/layouts/_user_menu.html.erb | Adds “View groups” to admin/curator/owner admin menu. |
| app/views/groups/index.html.erb | New Groups index UI. |
| app/views/groups/show.html.erb | New Groups show page with members + related spaces sections. |
| app/views/groups/new.html.erb | New group page. |
| app/views/groups/edit.html.erb | Edit group page. |
| app/views/groups/_form.html.erb | Group form with user autocompleter + owner flag selection. |
| app/views/groups/_group.html.erb | Group partial. |
| app/views/groups/_group.json.jbuilder | Group JSON representation. |
| app/views/groups/index.json.jbuilder | Groups index JSON. |
| app/views/groups/show.json.jbuilder | Group show JSON. |
| app/serializers/group_serializer.rb | Adds Group serializer for API/JSON:API usage. |
| app/policies/application_policy.rb | Adds shared shown? visibility logic for private-space scoping + docstrings. |
| app/policies/space_policy.rb | Aligns SpacePolicy show/edit/destroy with shown?/admin/space-admin rules. |
| app/policies/group_policy.rb | New GroupPolicy for member/admin visibility and HTML-only writes. |
| app/policies/material_policy.rb | Ensures Material show? also requires shown? (private-space visibility). |
| app/policies/event_policy.rb | Ensures Event show? also requires shown? (private-space visibility). |
| app/models/user.rb | Adds group/group_membership associations + owner helper methods. |
| app/models/space.rb | Adds groups association, private-space validation, and destroy-time handling + docstrings. |
| app/models/group.rb | New Group model with memberships and spaces associations. |
| app/models/group_membership.rb | New join model with owner flag and composite PK intent. |
| app/models/global_space.rb | Adjusts equality semantics for GlobalSpace. |
| app/helpers/groups_helper.rb | Adds Groups helper module. |
| app/controllers/groups_controller.rb | Adds Groups CRUD with membership owner syncing + policy enforcement. |
| app/controllers/spaces_controller.rb | Filters spaces#index by shown? and permits group_ids/is_private. |
| app/controllers/materials_controller.rb | Adds @results_count for index results. |
| app/controllers/concerns/searchable_index.rb | Filters Solr results by shown? and changes JSON:API meta keys. |
| app/controllers/application_controller.rb | Enforces private-space access at space resolution time (host-based). |
| app/assets/javascripts/templates/autocompleter/group_member.hbs | Autocompleter template for group membership rows (incl. owner checkbox). |
| app/assets/javascripts/templates/autocompleter/group_id.hbs | Autocompleter template for group selection in space form. |
| app/assets/javascripts/show_private_groups.js | Toggles required-groups UI based on “private space” checkbox. |
| app/assets/javascripts/autocompleters.js | Adds groups transform function for autocompleter responses. |
| app/assets/config/manifest.js | Includes show_private_groups.js in asset pipeline. |
💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.
| @spaces = Space.all.select { |space| policy(space).shown? } | ||
| respond_to do |format| | ||
| format.html | ||
| format.json { render json: @groups.as_json(only: [:id, :title]) } | ||
| end |
There was a problem hiding this comment.
Why removing @spaces = Space.all.select { |space| policy(space).shown? }? @spaces needs to be initialized.
| # Override total on the Solr result object so the view gets the right count | ||
| @search_results.instance_variable_set(:@total, filtered.length) | ||
| def @search_results.total; @total; end | ||
|
|
||
| @index_resources = WillPaginate::Collection.create(page, per_page, filtered.length) do |pager| | ||
| pager.replace(filtered) | ||
| end |
| # Returns:: the default Pundit policy scope for the record's class, | ||
| # filtered by #shown?. | ||
| def scope | ||
| Pundit.policy_scope!(user, record.class) | ||
| Pundit.policy_scope!(user, record.class).shown? | ||
| end |
| def is_owner_in_any_group? | ||
| Group.all.any? { |group| group.group_memberships.find_by(user: self)&.owner == true } | ||
| end |
| def validate(record) | ||
| Rails.logger.info "Validating space: is_private=#{record.is_private}, groups_count=#{record.groups.length}" | ||
| if record.is_private && record.groups.length == 0 | ||
| record.errors.add(:base, "If the space is private, you must add required groups.") | ||
| end | ||
| end |
| <%= button_to @group, method: :delete, | ||
| class: 'btn btn-danger', | ||
| form: { data: { confirm: "Are you sure you want to delete this group?" } } do %> | ||
| <i class="glyphicon glyphicon-trash"></i> Delete | ||
| <% end %> |
| @@ -0,0 +1,23 @@ | |||
| // excuted for space form | |||
| create_join_table :groups, :spaces do |t| | ||
| # t.index [:group_id, :space_id] | ||
| # t.index [:space_id, :group_id] | ||
| end |
| class AddIsPrivateToSpaces < ActiveRecord::Migration[7.2] | ||
| def change | ||
| add_column :spaces, :is_private, :boolean | ||
| end | ||
| end |
| meta: { | ||
| facets: facets, | ||
| available_facets: available_facets, | ||
| query: @search_params, | ||
| results_count: total | ||
| facets: facets, | ||
| :'available-facets' => available_facets, | ||
| query: @search_params, | ||
| :'results-count' => total | ||
| } |
|
I just ran the whole test suite on this branch, it works locally....... Maybe tests fail because of the gem audit failing as well? |
The audit failures can be ignored, the capybara tests possibly are an issue, but I notice that the screenshots that were produced are not accessible in any way. I'm looking into this: https://bibwild.wordpress.com/2024/12/05/accessing-capybara-screenshot-artifacts-on-github-ci/ |
Summary of changes
Database
3 new tables
Changes in existing tables
Code
New group controller and policy
New MemberShip Model
Shown? in application policy
Spaces controller and policy updates
space policy:
space controller:
Solr search_index updates
Application controller updates
Space model update
Rdoc
Screenshots
Checklist